﻿// -----------------------------------------------------------------------
// <copyright file="BookDataProvider.cs" company="EPAM Systems">
////Copyright (c) EPAM Systems, 2012. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace MBOP.DataAccess.DataProviders
{
	#region Usings

	using System.Collections.Generic;
	using System.Data;
	using System.Linq;

	using MBOP.Core.Entities.Common;
	using MBOP.Core.Entities.Library;
	using MBOP.Core.Interfaces.Common;
	using MBOP.Core.Interfaces.DataProviders;
	using MBOP.DataAccess.Contexts;
	using MBOP.Helpers.ExceptionHandling.ExceptionHandlers;

	#endregion

	/// <summary>
	/// The <see cref="IBookDataProvider"/> implementer
	/// which provides book's data using EF.
	/// </summary>
	public class BookDataProvider : IBookDataProvider
	{
		#region Constants

		/// <summary>
		/// Represents book image property name.
		/// </summary>
		private const string PROPERTY_NAME_IMAGE = "Image";

		/// <summary>
		/// Represents book image property name
		/// </summary>
		private const string PROPERTY_NAME_SUBSCRIPTIONS = "Subscriptions";

		#endregion

		#region Implementation IBookDataProvider

		#region Methods

		/// <summary>
		/// Adds book to database.
		/// </summary>
		/// <param name="newBook">The book.</param>
		public void AddBook(Book newBook)
		{
			try
			{
				using (DataContext context = new DataContext())
				{
					/*
					Tag tag = new Tag() { ID = 1 };

					context.Tags.Attach(tag);

					newBook.Tags = new List<Tag>();
					newBook.Tags.Add(tag);
					*/
					context.Books.Add(newBook);
					context.SaveChanges();
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}
		}

		/// <summary>
		/// Deletes book from database.
		/// </summary>
		/// <param name="bookID">The book id</param>
		/// <returns>The book id.</returns>
		public int DeleteBook(int bookID)
		{
			try
			{
				using (DataContext context = new DataContext())
				{
					Book bookToDelete = context.Books.Include(PROPERTY_NAME_IMAGE).Where(book => book.ID == bookID).SingleOrDefault();

					if (bookToDelete != null)
					{
						if (bookToDelete.Image != null)
						{
							if (bookToDelete.Image.Data != null)
							{
								context.Data.Remove(bookToDelete.Image.Data);
							}

							context.Images.Remove(bookToDelete.Image);
						}

						context.Books.Remove(bookToDelete);

						context.SaveChanges();
					}
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}

			return bookID;
		}

		/// <summary>
		/// Updates book in the database.
		/// </summary>
		/// <param name="bookToUpdate">The book.</param>
		public void UpdateBook(Book bookToUpdate)
		{
			try
			{
				using (DataContext context = new DataContext())
				{
					Book existingBook = context.Books.Single(book => book.ID == bookToUpdate.ID);

					SetBookFields(existingBook, bookToUpdate);

					context.SaveChanges();
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}
		}

		/// <summary>
		/// Gets the count of books in library by the specific filter.
		/// </summary>
		/// <param name="bookFilter">The book filter.</param>
		/// <returns>The count of elements in the sequence.</returns>
		public int GetBookCount(IFilter<Book> bookFilter)
		{
			int booksCount = 0;

			try
			{
				using (DataContext context = new DataContext())
				{
					booksCount = context.Books.Count(bookFilter.FilterCondition);
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}

			return booksCount;
		}

		/// <summary>
		/// Gets books from database.
		/// </summary>
		/// <param name="skipCount">The number of skipping books.</param>
		/// <param name="takeCount">The number of taking books.</param>
		/// <param name="bookFilter">The book filter.</param>
		/// <param name="sortInfo">The sort info.</param>
		/// <returns>The books collection.</returns>
		public IEnumerable<Book> GetBooks(int skipCount, int takeCount, IFilter<Book> bookFilter, SortInfo sortInfo)
		{
			List<Book> books = new List<Book>();

			try
			{
				using (DataContext context = new DataContext())
				{
					var booksQuery = context.Books
						.Include(PROPERTY_NAME_SUBSCRIPTIONS)
						.Include(PROPERTY_NAME_IMAGE)
						.Where(bookFilter.FilterCondition);

					if (sortInfo.GetUnderlyingSortType() == SortType.Asc)
					{
						switch (sortInfo.GetUnderlyingSortField())
						{
							case SortField.Title:
								booksQuery = booksQuery.OrderBy(book => book.Title);
								break;
							case SortField.Date:
								booksQuery = booksQuery.OrderBy(book => book.ID);
								break;
						}
					}
					else if (sortInfo.GetUnderlyingSortType() == SortType.Desc)
					{
						switch (sortInfo.GetUnderlyingSortField())
						{
							case SortField.Title:
								booksQuery = booksQuery.OrderByDescending(book => book.Title);
								break;
							case SortField.Date:
								booksQuery = booksQuery.OrderByDescending(book => book.ID);
								break;
						}
					}

					books = booksQuery.Skip(skipCount).Take(takeCount).ToList();
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}

			return books;
		}

		/// <summary>
		/// Gets the book by id.
		/// </summary>
		/// <param name="id">The book id</param>
		/// <returns>The book.</returns>
		public Book GetBookByID(int id)
		{
			Book requestedBook = new Book();

			try
			{
				using (DataContext context = new DataContext())
				{
					requestedBook = context.Books.Include(PROPERTY_NAME_SUBSCRIPTIONS).Include(PROPERTY_NAME_IMAGE).FirstOrDefault(book => book.ID == id);
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}

			return requestedBook;
		}

		/// <summary>
		/// Adds subscription to database.
		/// </summary>
		/// <param name="bookSubscription">The book subscription.</param>
		public void AddSubscription(BookSubscription bookSubscription)
		{
			try
			{
				using (DataContext context = new DataContext())
				{
					context.BooksSubscriptions.Add(bookSubscription);
					context.SaveChanges();
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}
		}

		/// <summary>
		/// Deletes all person subscription from current book.
		/// </summary>
		/// <param name="subscriptionSet">The subscription set</param>
		/// <param name="bookID">The book id</param>
		public void DeletePersonSubscriptions(string subscriptionSet, int bookID)
		{
			try
			{
				using (DataContext context = new DataContext())
				{
					foreach (var subscription in context.BooksSubscriptions.Where(subscription => subscription.SubscriptionSet == subscriptionSet
						&& subscription.BookID == bookID).ToList())
					{
						context.BooksSubscriptions.Remove(subscription);
					}

					context.SaveChanges();
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}
		}

		/// <summary>
		/// Gets the book subscriptions.
		/// </summary>
		/// <param name="bookID">The book id.</param>
		/// <returns>The book subscriptions collection</returns>
		public IEnumerable<BookSubscription> GetBooksSubscription(int bookID)
		{
			List<BookSubscription> requestedSubscriptions = new List<BookSubscription>();

			try
			{
				using (DataContext context = new DataContext())
				{
					requestedSubscriptions = context.BooksSubscriptions.Select(subscriptions => subscriptions)
						.Where(subscription => subscription.BookID == bookID)
						.OrderBy(subscription => subscription.ID).ToList();
				}
			}
			catch (DataException ex)
			{
				DataAccessExceptionHandler.HandleException(ex);
			}

			return requestedSubscriptions;
		}

		#endregion

		#endregion

		#region Private Methods

		/// <summary>
		/// Sets fields from bookToUpdate to existing book.
		/// </summary>
		/// <param name="existingBook">Updating book.</param>
		/// <param name="bookToUpdate">Book with new fields.</param>
		private static void SetBookFields(Book existingBook, Book bookToUpdate)
		{
			existingBook.ID = bookToUpdate.ID;
			existingBook.ISBN = bookToUpdate.ISBN;
			existingBook.EAN = bookToUpdate.EAN;
			existingBook.Authors = bookToUpdate.Authors;
			existingBook.Description = bookToUpdate.Description;
			existingBook.OwnerID = bookToUpdate.OwnerID;
			existingBook.Title = bookToUpdate.Title;
			existingBook.Subscriptions = bookToUpdate.Subscriptions;

			if (bookToUpdate.ImageID != null)
			{
				existingBook.Image = bookToUpdate.Image;
			}
		}

		#endregion
	}
}